/**
 * Copyright (c) 2011-2012, James Zhan 詹波 (jfinal@126.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jfinal.util.json;

import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.util.StringKit;

/**
 * Convert value of request to json string.
 * 
 *  Json     			java
 * string			java.lang.String
 * number			java.lang.Number
 * true|false		java.lang.Boolean
 * null				null
 * array			java.util.List
 * object			java.util.Map
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public final class JsonBuilder {
	
	private static final boolean isBasicType(Object v) {
		return (v instanceof String || v instanceof Number || v instanceof Boolean || v == null);
	}
	
	public static final String buildJson(String key, Object value) {
		JsonMap root = new JsonMap();
		if (isBasicType(value)) {
			root.put(key, value);
		}
		else if (value instanceof List) {
			root.put(key, buildJsonList((List)value));
		}
		else if (value instanceof Map) {
			root.put(key, buildJsonMap((Map)value));
		}
		else if (value instanceof Model) {
			root.put(key, buildModelMap((Model)value));
		}
		else if (value instanceof Record) {
			root.put(key, buildRecordMap((Record)value));
		}
		else {
			root.put(key, buildJavaBeanMap(value));
		}
		return root.toJson();
	}
	
	public static final String buildJson(HttpServletRequest request) {
		JsonMap root = new JsonMap();
		
		Enumeration<String> attrs = request.getAttributeNames();
		while (attrs.hasMoreElements()) {
			String key = attrs.nextElement();
			Object value = request.getAttribute(key);
			
			if (isBasicType(value)) {
				root.put(key, value);
			}
			else if (value instanceof List) {
				root.put(key, buildJsonList((List)value));
			}
			else if (value instanceof Map) {
				root.put(key, buildJsonMap((Map)value));
			}
			else if (value instanceof Model) {
				root.put(key, buildModelMap((Model)value));
			}
			else if (value instanceof Record) {
				root.put(key, buildRecordMap((Record)value));
			}
			else {
				root.put(key, buildJavaBeanMap(value));
			}
		}
		
		return root.toJson();
	}
	
	private static final JsonList buildJsonList(List list) {
		JsonList jsonList = new JsonList();
		for (Object value : list) {
			if (isBasicType(value)) {
				jsonList.add(value);
			}
			else if (value instanceof Model) {
				jsonList.add(buildModelMap((Model)value));
			}
			else if (value instanceof Record) {
				jsonList.add(buildRecordMap((Record)value));
			}
			else {
				jsonList.add(buildJavaBeanMap(value));
			}
		}
		return jsonList;
	}
	
	private static final JsonMap buildJsonMap(Map<String, Object> map) {
		JsonMap jsonMap = new JsonMap();
		for (Entry<String, Object> e : map.entrySet()) {
			String key = e.getKey();
			Object value = e.getValue();
			if (isBasicType(value)) {
				jsonMap.put(key, value);
			}
			else if (value instanceof Model) {
				jsonMap.put(key, buildModelMap((Model)value));
			}
			else if (value instanceof Record) {
				jsonMap.put(key, buildRecordMap((Record)value));
			}
			else {
				jsonMap.put(key, buildJavaBeanMap(value));
			}
		}
		return jsonMap;
	}
	
	private static JsonMap buildRecordMap(Record record) {
		return buildEntrySet(record.getColumns().entrySet());
	}
	
	private static final JsonMap buildModelMap(Model model) {
		return buildEntrySet(model.getAttrsEntrySet());
	}
	
	private static final JsonMap buildEntrySet(Set<Entry<String, Object>> entrySet) {
		JsonMap modelMap = new JsonMap();
		Set<Entry<String, Object>> attrsEntrySet = entrySet;
		for (Entry<String, Object> e : attrsEntrySet) {
			String attrName = e.getKey();
			Object value = e.getValue();
			if (isBasicType(value)) {
				modelMap.put(attrName, value);
			}
			else {
				modelMap.put(attrName, value.toString());
			}
		}
		return modelMap;
	}
	
	private static final JsonMap buildJavaBeanMap(Object model) {
		JsonMap map = new JsonMap();
		Method[] methods = model.getClass().getMethods();
		for (Method m : methods) {
			String methodName = m.getName();
			int indexOfSet = methodName.indexOf("get");
			if (indexOfSet == 0 && methodName.length() > 3) {
				String attrName = methodName.substring(3);
				if (!attrName.equals("Class")) {
					Class<?>[] types = m.getParameterTypes();
					if (types.length == 0) {
						try {
							Object value = m.invoke(model);
							map.put(StringKit.firstCharToLowerCase(attrName), value);
						} catch (Exception e) {
							throw new RuntimeException(e.getMessage(), e);
						}
					}
				}
			}
		}
		return map;
	}
	
	public static String buildJson(HttpServletRequest request, String[] attrs) {
		JsonMap root = new JsonMap();
		
		for (String key : attrs) {
			Object value = request.getAttribute(key);
			
			if (isBasicType(value)) {
				root.put(key, value);
			}
			else if (value instanceof List) {
				root.put(key, buildJsonList((List)value));
			}
			else if (value instanceof Map) {
				root.put(key, buildJsonMap((Map)value));
			}
			else if (value instanceof Model) {
				root.put(key, buildModelMap((Model)value));
			}
			else if (value instanceof Record) {
				root.put(key, buildRecordMap((Record)value));
			}
			else {
				root.put(key, buildJavaBeanMap(value));
			}
		}
		
		return root.toJson();
	}
}










